#include "RowsModel.h"
#include "EventWrapper.h"
#include "RemoveRowNetworkEvent.h"
#include "AddRowNetworkEvent.h"
#include "MoveRowNetworkEvent.h"
#include "UpdateScoreInternalEvent.h"
#include "DeclareWinnerInternalEvent.h"

#include <algorithm>

RowsModel::RowsModel(PlayersPtr players, unsigned int widthOfWell)
    : players(players), widthOfWell(widthOfWell)
{
}

RowsModel::~RowsModel()
{
}

void RowsModel::init()
{
    addRows(20);
}

void RowsModel::addRows(int noOfRows)
{
    for (int i = noOfRows - 1; i >= 0; --i)
    {
        addRow(i);
    }
}

void RowsModel::addRow(int rowNo)
{
    RowModel* rowModel = new RowModel(widthOfWell);
    addChild(rowModel);
    rows.insert(rows.begin(), rowModel);
    addChangeEvent(new AddRowNetworkEvent(rowModel->getId(), rowNo));
}

void RowsModel::removeFullRows(int playerId)
{
    RowModels rowsToRemove = getFullRows();
    for (RowModels::iterator it = rowsToRemove.begin(); it != rowsToRemove.end(); ++it)
    {
        removeFullRow(*it);
    }
    addRows(rowsToRemove.size());
    updateScore(playerId, rowsToRemove.size());
}

void RowsModel::removeFullRow(RowModel* row)
{
    RowModels::iterator fullRowIt = std::find(rows.begin(), rows.end(), row);
    long rowId = row->getId();
    addChangeEvent(new RemoveRowNetworkEvent(rowId));
    for (RowModels::iterator rowsAboveIt = rows.begin(); rowsAboveIt != fullRowIt; ++rowsAboveIt)
    {
        addChangeEvent(new MoveRowNetworkEvent((*rowsAboveIt)->getId()));
    }
    rows.erase(fullRowIt);
    removeChild(rowId);
}

RowModels RowsModel::getFullRows()
{
    RowModels result;
    for (RowModels::iterator it = rows.begin(); it != rows.end(); ++it)
    {
        if ((*it)->isFull())
        {
            result.push_back(*it);
        }
    }
    return result;
}

void RowsModel::updateScore(int playerId, int noOfRows)
{
    if (noOfRows > 0)
    {
        EventWrapper::push(new UpdateScoreInternalEvent(playerId, UpdateScoreInternalEvent::FULL_ROW, noOfRows));
    }
}

bool RowsModel::hasEmptySpaceAt(const Position& position)
{
    return isInsideWell(position) && noExistingBlock(position);
}

bool RowsModel::isInsideWell(const Position& position)
{
    return (position.y < 20) && (position.x >= 0) && (position.x < (int)widthOfWell);
}

bool RowsModel::noExistingBlock(const Position& position)
{
    if (position.y < 0)
    {
        return true;
    }
    return rows[position.y]->noExistingBlock(position);
}

void RowsModel::addBlocksAt(const Positions& positions, int playerId)
{
    Color color = players->getPlayerColor(playerId);
    for (Positions::const_iterator it = positions.begin(); it != positions.end(); ++it)
    {
        if ((*it).y > 0)
        {
            rows[(*it).y]->addBlock(playerId, Position((*it).x, 0), color);
        }
        else
        {
            EventWrapper::push(new DeclareWinnerInternalEvent());
        }
    }
}
